/***
 |''Name''|tiddlywiki.js|
 |''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror|
 |''Author''|PMario|
 |''Version''|0.1.7|
 |''Status''|''stable''|
 |''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]|
 |''Documentation''|http://codemirror.tiddlyspace.com/|
 |''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]|
 |''CoreVersion''|2.5.0|
 |''Requires''|codemirror.js|
 |''Keywords''|syntax highlighting color code mirror codemirror|
 ! Info
 CoreVersion parameter is needed for TiddlyWiki only!
 ***/
//{{{
CodeMirror.defineMode("tiddlywiki", function (config, parserConfig) {
    var indentUnit = config.indentUnit;

    // Tokenizer
    var textwords = function () {
        function kw(type) {
            return {
                type: type,
                style: "text"
            };
        }

        return {};
    }();

    var keywords = function () {
        function kw(type) {
            return {type: type, style: "macro"};
        }

        return {
            "allTags": kw('allTags'), "closeAll": kw('closeAll'), "list": kw('list'),
            "newJournal": kw('newJournal'), "newTiddler": kw('newTiddler'),
            "permaview": kw('permaview'), "saveChanges": kw('saveChanges'),
            "search": kw('search'), "slider": kw('slider'), "tabs": kw('tabs'),
            "tag": kw('tag'), "tagging": kw('tagging'), "tags": kw('tags'),
            "tiddler": kw('tiddler'), "timeline": kw('timeline'),
            "today": kw('today'), "version": kw('version'), "option": kw('option'),

            "with": kw('with'),
            "filter": kw('filter')
        };
    }();

    var isSpaceName = /[\w_\-]/i,
        reHR = /^\-\-\-\-+$/,					// <hr>
        reWikiCommentStart = /^\/\*\*\*$/,		// /***
        reWikiCommentStop = /^\*\*\*\/$/,		// ***/
        reBlockQuote = /^<<<$/,

        reJsCodeStart = /^\/\/\{\{\{$/,			// //{{{ js block start
        reJsCodeStop = /^\/\/\}\}\}$/,			// //}}} js stop
        reXmlCodeStart = /^<!--\{\{\{-->$/,		// xml block start
        reXmlCodeStop = /^<!--\}\}\}-->$/,		// xml stop

        reCodeBlockStart = /^\{\{\{$/,			// {{{ TW text div block start
        reCodeBlockStop = /^\}\}\}$/,			// }}} TW text stop

        reCodeStart = /\{\{\{/,					// {{{ code span start
        reUntilCodeStop = /.*?\}\}\}/;

    function chain(stream, state, f) {
        state.tokenize = f;
        return f(stream, state);
    }

    // used for strings
    function nextUntilUnescaped(stream, end) {
        var escaped = false,
            next;
        while ((next = stream.next()) != null) {
            if (next == end && !escaped) return false;
            escaped = !escaped && next == "\\";
        }
        return escaped;
    }

    // Used as scratch variables to communicate multiple values without
    // consing up tons of objects.
    var type, content;

    function ret(tp, style, cont) {
        type = tp;
        content = cont;
        return style;
    }

    function jsTokenBase(stream, state) {
        var sol = stream.sol(),
            ch, tch;

        state.block = false;	// indicates the start of a code block.

        ch = stream.peek(); 	// don't eat, to make matching simpler

        // check start of  blocks
        if (sol && /[<\/\*{}\-]/.test(ch)) {
            if (stream.match(reCodeBlockStart)) {
                state.block = true;
                return chain(stream, state, twTokenCode);
            }
            if (stream.match(reBlockQuote)) {
                return ret('quote', 'quote');
            }
            if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) {
                return ret('code', 'comment');
            }
            if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) {
                return ret('code', 'comment');
            }
            if (stream.match(reHR)) {
                return ret('hr', 'hr');
            }
        } // sol
        ch = stream.next();

        if (sol && /[\/\*!#;:>|]/.test(ch)) {
            if (ch == "!") { // tw header
                stream.skipToEnd();
                return ret("header", "header");
            }
            if (ch == "*") { // tw list
                stream.eatWhile('*');
                return ret("list", "comment");
            }
            if (ch == "#") { // tw numbered list
                stream.eatWhile('#');
                return ret("list", "comment");
            }
            if (ch == ";") { // definition list, term
                stream.eatWhile(';');
                return ret("list", "comment");
            }
            if (ch == ":") { // definition list, description
                stream.eatWhile(':');
                return ret("list", "comment");
            }
            if (ch == ">") { // single line quote
                stream.eatWhile(">");
                return ret("quote", "quote");
            }
            if (ch == '|') {
                return ret('table', 'header');
            }
        }

        if (ch == '{' && stream.match(/\{\{/)) {
            return chain(stream, state, twTokenCode);
        }

        // rudimentary html:// file:// link matching. TW knows much more ...
        if (/[hf]/i.test(ch)) {
            if (/[ti]/i.test(stream.peek()) && stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) {
                return ret("link", "link");
            }
        }
        // just a little string indicator, don't want to have the whole string covered
        if (ch == '"') {
            return ret('string', 'string');
        }
        if (ch == '~') {	// _no_ CamelCase indicator should be bold
            return ret('text', 'brace');
        }
        if (/[\[\]]/.test(ch)) { // check for [[..]]
            if (stream.peek() == ch) {
                stream.next();
                return ret('brace', 'brace');
            }
        }
        if (ch == "@") {	// check for space link. TODO fix @@...@@ highlighting
            stream.eatWhile(isSpaceName);
            return ret("link", "link");
        }
        if (/\d/.test(ch)) {	// numbers
            stream.eatWhile(/\d/);
            return ret("number", "number");
        }
        if (ch == "/") { // tw invisible comment
            if (stream.eat("%")) {
                return chain(stream, state, twTokenComment);
            }
            else if (stream.eat("/")) { //
                return chain(stream, state, twTokenEm);
            }
        }
        if (ch == "_") { // tw underline
            if (stream.eat("_")) {
                return chain(stream, state, twTokenUnderline);
            }
        }
        // strikethrough and mdash handling
        if (ch == "-") {
            if (stream.eat("-")) {
                // if strikethrough looks ugly, change CSS.
                if (stream.peek() != ' ')
                    return chain(stream, state, twTokenStrike);
                // mdash
                if (stream.peek() == ' ')
                    return ret('text', 'brace');
            }
        }
        if (ch == "'") { // tw bold
            if (stream.eat("'")) {
                return chain(stream, state, twTokenStrong);
            }
        }
        if (ch == "<") { // tw macro
            if (stream.eat("<")) {
                return chain(stream, state, twTokenMacro);
            }
        }
        else {
            return ret(ch);
        }

        // core macro handling
        stream.eatWhile(/[\w\$_]/);
        var word = stream.current(),
            known = textwords.propertyIsEnumerable(word) && textwords[word];

        return known ? ret(known.type, known.style, word) : ret("text", null, word);

    } // jsTokenBase()

    function twTokenString(quote) {
        return function (stream, state) {
            if (!nextUntilUnescaped(stream, quote)) state.tokenize = jsTokenBase;
            return ret("string", "string");
        };
    }

    // tw invisible comment
    function twTokenComment(stream, state) {
        var maybeEnd = false,
            ch;
        while (ch = stream.next()) {
            if (ch == "/" && maybeEnd) {
                state.tokenize = jsTokenBase;
                break;
            }
            maybeEnd = (ch == "%");
        }
        return ret("comment", "comment");
    }

    // tw strong / bold
    function twTokenStrong(stream, state) {
        var maybeEnd = false,
            ch;
        while (ch = stream.next()) {
            if (ch == "'" && maybeEnd) {
                state.tokenize = jsTokenBase;
                break;
            }
            maybeEnd = (ch == "'");
        }
        return ret("text", "strong");
    }

    // tw code
    function twTokenCode(stream, state) {
        var ch, sb = state.block;

        if (sb && stream.current()) {
            return ret("code", "comment");
        }

        if (!sb && stream.match(reUntilCodeStop)) {
            state.tokenize = jsTokenBase;
            return ret("code", "comment");
        }

        if (sb && stream.sol() && stream.match(reCodeBlockStop)) {
            state.tokenize = jsTokenBase;
            return ret("code", "comment");
        }

        ch = stream.next();
        return (sb) ? ret("code", "comment") : ret("code", "comment");
    }

    // tw em / italic
    function twTokenEm(stream, state) {
        var maybeEnd = false,
            ch;
        while (ch = stream.next()) {
            if (ch == "/" && maybeEnd) {
                state.tokenize = jsTokenBase;
                break;
            }
            maybeEnd = (ch == "/");
        }
        return ret("text", "em");
    }

    // tw underlined text
    function twTokenUnderline(stream, state) {
        var maybeEnd = false,
            ch;
        while (ch = stream.next()) {
            if (ch == "_" && maybeEnd) {
                state.tokenize = jsTokenBase;
                break;
            }
            maybeEnd = (ch == "_");
        }
        return ret("text", "underlined");
    }

    // tw strike through text looks ugly
    // change CSS if needed
    function twTokenStrike(stream, state) {
        var maybeEnd = false,
            ch, nr;

        while (ch = stream.next()) {
            if (ch == "-" && maybeEnd) {
                state.tokenize = jsTokenBase;
                break;
            }
            maybeEnd = (ch == "-");
        }
        return ret("text", "strikethrough");
    }

    // macro
    function twTokenMacro(stream, state) {
        var ch, tmp, word, known;

        if (stream.current() == '<<') {
            return ret('brace', 'macro');
        }

        ch = stream.next();
        if (!ch) {
            state.tokenize = jsTokenBase;
            return ret(ch);
        }
        if (ch == ">") {
            if (stream.peek() == '>') {
                stream.next();
                state.tokenize = jsTokenBase;
                return ret("brace", "macro");
            }
        }

        stream.eatWhile(/[\w\$_]/);
        word = stream.current();
        known = keywords.propertyIsEnumerable(word) && keywords[word];

        if (known) {
            return ret(known.type, known.style, word);
        }
        else {
            return ret("macro", null, word);
        }
    }

    // Interface
    return {
        startState: function (basecolumn) {
            return {
                tokenize: jsTokenBase,
                indented: 0,
                level: 0
            };
        },

        token: function (stream, state) {
            if (stream.eatSpace()) return null;
            var style = state.tokenize(stream, state);
            return style;
        },

        electricChars: ""
    };
});

CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki");
//}}}
